<html>
<head>
    <meta charset="utf-8"/>
    <title>数独辅助工具</title>
    <meta name="viewport" content="width=device-width, initial-scale=1"/>
    <script type="text/javascript" src="https://cdn.bootcss.com/jquery/3.2.1/jquery.min.js"></script>
    <script type="text/javascript">
        // http://localhost:63342/sd/sd.html?_ijt=40bm67dv10ukl80ths74v2lgjt&_ij_reload=RELOAD_ON_SAVE&s=A100300006B043008020C800070400D050800300E060710000F000045001G000080074H005000603I002400900

        // http://localhost:63342/sd/sd.html?_ijt=40bm67dv10ukl80ths74v2lgjt&_ij_reload=RELOAD_ON_SAVE&s=A100304006B543008120C800070430D051800340E064710000F000045061G000080074H405000603I002400900

        // 操作记录, 用于后退操作
        var backList = new Array();
        // 棋盘
        var bArray = new Array(9);
        // 当前状态
        var type = 'static';
        // 键盘上下左右的光标位置
        var keyX = 1, keyY = 1;

        $(document).ready(function() {
            // 初始化
            init();
            // 从 url 载入存档
            loadByUrl();
            // 鼠标点击事件
            $("#view").on('click', '.s', function() {
                $t = $(this), x = $t.data("i"), y = $t.data("j"), k = $t.data("k");
                showMap({x:x, y:y, num:k})
            });
            // 键盘点击事件
            $(document).keydown(function(event){keyAction(event)});
        });

        // 从 url 载入存档
        function loadByUrl() {
            // 从 url 载入存档
            var json = getQueryString('j','');
            if (json) {
                json = json.replaceAll('a','[').replaceAll('z',']').replaceAll('c',',').replaceAll('o','null')
                bArray = JSON.parse(json);
                // 渲染页面
                showMap();
            }
            var save = getQueryString('s','');
            if (save) {
                bArray = new Array(9);
                for (var i = 1; i <= 9; i++) {
                    bArray[i] = new Array(9)
                    for (var j = 1; j <= 9; j++) {
                        bArray[i][j] = parseInt(save.substr((i-1)*10+j, 1))
                    }
                }
                showMap();
            }
        }

        // 初始化
        function init() {
            backList = new Array();
            keyX = 1, keyY = 1;
            $("#view tbody").children().remove();
            for (var i = 1; i <= 9; i++) {
                bArray[i] = new Array(9);
                var $tr = $('#template tr').clone();
                $tr.children().remove();
                for (var j = 1; j <= 9; j++) {
                    bArray[i][j] = 0;

                    var $td = $('#template tr td').clone();
                    $td.children().remove();
                    if ((Math.floor((i-1)/3)+Math.floor((j-1)/3))%2 == 0) {
                        $td.addClass('bg2');
                    }
                    $td.addClass("d"+i+j);
                    for (var k = 1; k <= 9; k++) {
                       var $sp = $('#template tr td span.s').clone();
                       $sp.data('i', i);
                       $sp.data('j', j);
                       $sp.data('k', k);
                       $sp.addClass("s"+i+j+k);
                       $sp.text(k);
                       $td.append($sp);
                    }
                    $tr.append($td);
                }
                $('#view tbody').append($tr);
            }
        }
        // 键盘点击事件
        function keyAction(event){
            $(".put").removeClass("put");
            console.log("Key: "+event.keyCode);
            if(event.keyCode == 13 || event.keyCode == 32) {
                // 回车
                keyY ++;
                if (keyY > 9) {
                    keyY = 1, keyX ++;
                }
            } else if (event.keyCode == 38){
                // 上
                keyX = keyX > 1 ? keyX-1 : 9;
            } else if (event.keyCode == 40){
                // 下
                keyX = keyX < 9 ? keyX+1 : 1;
            } else if (event.keyCode == 37){
                // 左
                keyY = keyY > 1 ? keyY-1 : 9;
            } else if (event.keyCode == 39){
                // 右
                keyY = keyY < 9 ? keyY+1 : 1;
            } else if (event.keyCode < 48 || event.keyCode > 58) {
                return;
            } else {
                // 数字
                var keyNum = event.keyCode - 48;
                console.log("num: "+keyNum);
                if (keyNum != 0) {
                    showMap({x:keyX, y:keyY, num:keyNum})
                }
                keyY ++;
                if (keyY > 9) {
                    keyY = 1;
                    keyX ++;
                }
            }
            if (keyX > 9 || keyY > 9) {
                keyX=9, keyY=9;
            }
            $('.d'+keyX+keyY).addClass('put');
        }
        // 后退
        function back(size){
            if (size == 0) {
                showMap();
            } else if (backList.length == 0) {
                init();
            } else {
                var backEnter = backList.pop();
                bArray = backEnter.bArray;
                showMap();
            }
        }
        // 点击事件  ck = {x:1, y:1, num:1};
        function showMap(ck) {
            $(".prompt").text("");

            if (!ck) {
                // 重新渲染页面
                show(getSArray(bArray));
                return;
            }

            if (bArray[ck.x][ck.y] != 0) {
                if (ck.num != bArray[ck.x][ck.y]) {
                    return;
                }
                // 点击已有值的格子, 取消已选择的值
                bArray[ck.x][ck.y]=0;
                show(getSArray(bArray));
                return;
            }

            // 备份修改前的值
            var backupBArray = JSON.parse(JSON.stringify(bArray));

            sArray = getSArray(bArray);

            // 重新评估可用数字
            if (!verify(ck, bArray, sArray)) {
                // 验证失败
                show(sArray);
                $(".prompt").text("操作错误");
                return;
            }

            // 报错历史
            if (ck) {
                backList.push({
                    bArray: backupBArray
                });
            }

            bArray[ck.x][ck.y]=k;
            keyX = ck.x, keyY=ck.y;
            show(getSArray(bArray));
        }


        // 标记可用小数字
        function getSArray(bArray) {
            // 棋盘小数字, 小数字有3种值: def, dis, static
            var sArray = new Array(9);
            for (var i = 1; i <= 9; i++) {
                sArray[i] = new Array(9)
                for (var j = 1; j <= 9; j++) {
                    sArray[i][j] = new Array(9);
                    for (var k = 1; k <= 9; k++) {
                        if (bArray[i][j] == 0) {
                            sArray[i][j][k] = 'def';
                            // 行列是否已占用
                            for (var x = 1; x <= 9; x++) {8
                                if (bArray[x][j]==k && i != x) {
                                    sArray[i][j][k] = 'dis';
                                    break;
                                }
                                if (bArray[i][x]==k && j != x) {
                                    sArray[i][j][k] = 'dis';
                                    break;
                                }
                            }
                            // 同组九宫格
                            ab:
                            for (var a = Math.floor((i-1)/3)*3+1; a <= Math.floor((i-1)/3)*3+3; a++) {
                                for (var b = Math.floor((j-1)/3)*3+1; b <= Math.floor((j-1)/3)*3+3; b++) {
                                    if (bArray[a][b]==k && (a != i || b != j)) {
                                        sArray[i][j][k] = 'dis';
                                        break ab;
                                    }
                                }
                            }
                        } else if (bArray[i][j] == k) {
                            sArray[i][j][k] = 'static';
                        } else {
                            sArray[i][j][k] = 'dis';
                        }
                    }

                    var numSize = 0;
                    var numInt = 0;
                    for (var k = 1; k <= 9; k++) {
                        if (sArray[i][j][k] == 'def') {
                            numSize++;
                            numInt = k;
                        }
                    }
                    if (numSize == 1 && sArray[i][j][numInt] != 'static') {
                        sArray[i][j][numInt] = "only";
                    }
                }
            }



            for (var k = 1; k <= 9; k++) {
                for (var i = 1; i <= 9; i++) {
                    // 行列是否仅剩唯一
                    var disCount = 0;
                    var m=0, n=0;
                    for (var x = 1; x <= 9; x++) {
                        if (sArray[x][i][k] == 'dis') {
                            disCount++;
                        } else {
                            m=x, n=i;
                        }
                    }
                    if (disCount == 8 && sArray[m][n][k] != 'static') {
                        sArray[m][n][k] = "only";
                    }
                    disCount = 0;
                    for (var x = 1; x <= 9; x++) {
                        if (sArray[i][x][k] == 'dis') {
                            disCount++;
                        } else {
                            m=i, n=x;
                        }
                    }
                    if (disCount == 8 && sArray[m][n][k] != 'static') {
                        sArray[m][n][k] = "only";
                    }
                }
                // 同组九宫格是否仅剩唯一
                for (var i = 1; i <= 9; i=i+3) {
                    for (var j = 1; j <= 9; j=j+3) {
                        var disCount = 0;
                        var m=0, n=0;
                        for (var a = Math.floor((i-1)/3)*3+1; a <= Math.floor((i-1)/3)*3+3; a++) {
                            for (var b = Math.floor((j-1)/3)*3+1; b <= Math.floor((j-1)/3)*3+3; b++) {
                                if (sArray[a][b][k] == 'dis') {
                                    disCount++;
                                } else {
                                    m=a, n=b;
                                }
                            }
                        }
                        if (disCount == 8 && sArray[m][n][k] != 'static') {
                            sArray[m][n][k] = "only";
                        }
                    }
                }
            }
            return sArray;
        }

        function verify(ck, bArray, sArray) {
            // 行列
            var error = 0;
            for (var a = 1; a <= 9; a++) {
                if ((bArray[a][ck.y]==ck.num || sArray[a][ck.y][ck.num] == 'only') && ck.x!= a) {
                    // $(".prompt").text(x+"行"+ck.y+"列中已有数字" + ck.num);
                    sArray[a][ck.y][k]='error';
                    error++;
                }
                if ((bArray[ck.x][a]==ck.num || sArray[ck.x][a][ck.num] == 'only') && ck.y != a) {
                    // $(".prompt").text(ck.x+"行"+x+"列中已有数字" + ck.num);
                    sArray[ck.x][a][k]='error';
                    error++;
                }
            }
            // 同组九宫格
            for (var a = Math.floor((ck.x-1)/3)*3+1; a <= Math.floor((ck.x-1)/3)*3+3; a++) {
                for (var b = Math.floor((ck.y-1)/3)*3+1; b <= Math.floor((ck.y-1)/3)*3+3; b++) {
                    if ((bArray[a][b] == ck.num || sArray[a][b][ck.num] == 'only') && (a != ck.x || b != ck.y)) {
                        // $(".prompt").text(ck.x+"行"+ck.y+"列中已有数字" + k);
                        sArray[a][b][k]='error';
                        error++;
                    }
                }
            }
            return error == 0;
        }

        function show(sArray) {
            $(".s").removeClass("enable error static dis only val1 val2 val3 val4 val5 val6 val7 val8 val9");
            $("#view").removeClass("val1 val2 val3 val4 val5 val6 val7 val8 val9");

            var sum = [0,0,0,0,0,0,0,0,0,0];
            for (var i = 1; i <= 9; i++) {
                for (var j = 1; j <= 9; j++) {
                    for (var k = 1; k <= 9; k++) {
                        if (sArray[i][j][k] == 'static') {
                            sum[k]++;
                        }
                    }
                }
            }
            for (var i = 1; i <= 9; i++) {
                $(".sum_"+i).text(sum[i]);
                $(".sum_"+i).addClass('val'+sum[i]);
                if (sum[i] == 9) {
                    $("#view").addClass('val'+i);
                }
            }

            for (var i = 1; i <= 9; i++) {
                for (var j = 1; j <= 9; j++) {
                    for (var k = 1; k <= 9; k++) {
                        $(".s"+i+j+k).addClass(sArray[i][j][k]);
                        $(".s"+i+j+k).addClass('val'+k);
                    }
                }
            }

            // 将存档更新到 url 上
            var save = "";
            for (var i = 1; i <= 9; i++) {
                save = save + String.fromCharCode(64+i);
                for (var j = 1; j <= 9; j++) {
                    save = save + (bArray[i][j]? bArray[i][j] : '0');
                }
            }
            url2 = changeURLPar(window.location.href.toString(), "s", save);
            window.history.pushState({},0,url2);
        }


/**
 * 获取指定请求参数.
 *
 * @author sybn
 * @param name 参数名 必填
 * @param defaults 默认值, 可选
 * @param url 可选, 默认为当前页url
 */
function getQueryString(name, defaults, url) {
  var reg = new RegExp("(^|\\?|&)" + name + "=([^&]*)(&|$)", "i");
  var r = (url) ? url.match(reg) : window.location.search.substr(1).match(reg);
  if (r != null) return decodeURIComponent(r[2]);
  return typeof defaults != undefined ? defaults : null;
}

/**
 * 替换url中的参数
 */
function changeURLPar(url, key, value) {
  var replaceText = key + '=' + (value == null || typeof(value) == undefined ? "" : encodeURIComponent(value));
  if (url.match('[?&]' + key + '=([^&]*)')) {
    return url.replace(eval('/' + key + '=[^&]*/'), replaceText);
  } else {
    if (url.match('\\?'))  {
      return url + '&' + replaceText;
    } else {
      return url + '?' + replaceText;
    }
  }
}

/**
 * 删除url中的参数
 */
function removeURLPar(url, key) {
  if (url.match('[?&]' + key + '=([^&]*)')) {
    return url.replace(eval('/' + key + '=[^&]*/'), "");
  }
  return url;
}
/**
 * 批量替换 类似replace();
 */
String.prototype.replaceAll = function(regx,s2){
    return this.replace(new RegExp(regx,"gm"),s2);
}
    </script>
    <style>
        * {-moz-user-select:none;-webkit-user-select:none;user-select:none;}
        td {width:60px;height:60px;border: 1px solid #BBB}
        td.put {width:60px;height:60px;border: 1px solid #36F; background-color:#def !important}
        td .s{width:20px;height:20px;color:#666}
        td.bg2 {background-color:#EEE}
        .big {display:block}
        .s {display:inline-block;text-align:center}
        tbody .s:hover {background-color: #f96}
        .s.dis {color:#DDD;}
        .s.static, .s.sum_s{color:#33F;background-color: #abF}
        .s.only {color:#33F;}
        .enable.big {color:369}
        .static.big {color:000}
        .disable {color:#EEE}
        .s.error {color:#F00 !important}
        #view tfoot td {height:30px;border: 1px solid #36F; background-color:#def !important;text-a;text-align: center;}
        .sum_num {color:#777}
        .sum_num .val9{color:#3a3}
        .sum_num b {color:#36F}
        table.val1 .val1.static,table.val2 .val2.static,table.val3 .val3.static,table.val4 .val4.static,table.val5 .val5.static,table.val6 .val6.static,table.val7 .val7.static,table.val8 .val8.static,table.val9 .val9.static {background-color: #BFB}
        table.val1 .sum_s_1.sum_s,table.val2 .sum_s_2.sum_s,table.val3 .sum_s_3.sum_s,table.val4 .sum_s_4.sum_s,table.val5 .sum_s_5.sum_s,table.val6 .sum_s_6.sum_s,table.val7 .sum_s_7.sum_s,table.val8 .sum_s_8.sum_s,table.val9 .sum_s_9.sum_s {background-color: #BFB}
    </style>
</head>
<body>
<p>
    <!--    <button onclick="type='static'">初始</button>-->
    <!--    <button onclick="type='enable'">填写</button>-->
    <button onclick="back(0)">清除错误信息</button>
    <button onclick="back()">后退</button>
    <button onclick="init()">清空</button>
    <span class="prompt"></span>
    <span class="prompt2">请使用手机横屏模式, 或平板电脑打开</span>
</p>
<table id="view" border="1" cellspacing="0">
    <tbody>

    </tbody>
    <tfoot>
        <tr class="sum_num">
            <td><span class="s sum_s sum_s_1">1</span>x<span class="s sum_1">0</span></td>
            <td><span class="s sum_s sum_s_2">2</span>x<span class="s sum_2">0</span></td>
            <td><span class="s sum_s sum_s_3">3</span>x<span class="s sum_3">0</span></td>
            <td><span class="s sum_s sum_s_4">4</span>x<span class="s sum_4">0</span></td>
            <td><span class="s sum_s sum_s_5">5</span>x<span class="s sum_5">0</span></td>
            <td><span class="s sum_s sum_s_6">6</span>x<span class="s sum_6">0</span></td>
            <td><span class="s sum_s sum_s_7">7</span>x<span class="s sum_7">0</span></td>
            <td><span class="s sum_s sum_s_8">8</span>x<span class="s sum_8">0</span></td>
            <td><span class="s sum_s sum_s_9">9</span>x<span class="s sum_9">0</span></td>
        </tr>
    </tfoot>
</table>
<table id="template" style="display:none">
    <tr>
        <td>
            <span class="s"></span>
            <span class="big"></span>
        </td>
    </tr>
</table>
</body>
</html>